package xj.graph2d;

import static xj.graph2d.DrawShapeConstants.PointEastEdge;
import static xj.graph2d.DrawShapeConstants.PointInBounds;
import static xj.graph2d.DrawShapeConstants.PointInside;
import static xj.graph2d.DrawShapeConstants.PointNECorner;
import static xj.graph2d.DrawShapeConstants.PointNWCorner;
import static xj.graph2d.DrawShapeConstants.PointNorthEdge;
import static xj.graph2d.DrawShapeConstants.PointOnRotationHandle;
import static xj.graph2d.DrawShapeConstants.PointOutside;
import static xj.graph2d.DrawShapeConstants.PointSECorner;
import static xj.graph2d.DrawShapeConstants.PointSWCorner;
import static xj.graph2d.DrawShapeConstants.PointSouthEdge;
import static xj.graph2d.DrawShapeConstants.PointWestEdge;
import static xj.graph2d.DrawShapeConstants.d;
import static xj.graph2d.DrawShapeConstants.handleLength;

import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Dimension2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.HashMap;
import java.util.Map;

import xj.graph2d.graph.Dimension2DFloat;
import xj.graph2d.graph.Geometry;
import xj.graph2d.util.DrawingUtilities;

public class EllipseShape extends TwoEndsNodeShape implements Rotatable {

  public static final int DECO_CROSS = 1000;

  public static final int DECO_DOT = 1001;

  public EllipseShape() {
    this(false);
  }

  public EllipseShape(boolean filled) {
    this.filled = filled;
  }

  public EllipseShape(boolean filled, boolean createComp) {
    super(createComp);
    this.filled = filled;
  }

  public EllipseShape(float x1, float y1, float x2, float y2) {
    this(x1, y1, x2, y2, false);
  }

  public EllipseShape(float x1, float y1, float x2, float y2, boolean filled) {
    this.filled = filled;
    setEnds(x1, y1, x2, y2);
  }

  public EllipseShape(float x1, float y1, float x2, float y2, boolean filled,
      boolean createComp) {
    super(createComp);
    this.filled = filled;
    setEnds(x1, y1, x2, y2);
  }

  public DrawShape makeInstance() {
    return new EllipseShape(filled, comp != null);
  }

  @Override
  public String getName() {
    return "Ellipse";
  }

  @Override
  public void draw(Graphics2D g2) {
    float x = (float) Math.min(p1.getX(), p2.getX());
    float y = (float) Math.min(p1.getY(), p2.getY());
    float w = (float) Math.abs(p1.getX() - p2.getX());
    float h = (float) Math.abs(p1.getY() - p2.getY());
    Shape shape = new Ellipse2D.Float(x, y, w, h);
    if (filled) {
      fillBackground(g2, x, y, w, h, shape);
      if (showborder) {
	g2.draw(shape);
      }
    } else {
      g2.draw(shape);
    }

    if (deco == DECO_CROSS) {
      float f = 0.13f;
      float w1 = w * f;
      float h1 = h * f;
      g2.draw(new Line2D.Float(x + w1, y + h1, x + w - w1, y + h - h1));
      g2.draw(new Line2D.Float(x + w1, y + h - h1, x + w - w1, y + h1));
    } else if (deco == DECO_DOT) {
      float f = 0.2f;
      int w1 = (int) Math.floor(w * f);
      int h1 = (int) Math.floor(h * f);
      shape = new Ellipse2D.Float(x + w1, y + h1, w - 2 * w1, h - 2 * h1);
      fillBackground(g2, x + w1, y + h1, w - 2 * w1, h - 2 * h1, shape);
      //fillBackground(g2, x, y, w, h, shape);
      //g2.fill(shape);
    }
  }

  @Override
  public void drawOutline(Graphics2D g2, float x1, float y1, float x2, float y2) {
    DrawAttr attr = getDrawAttr();
    if (attr != null) {
      g2.setColor(attr.getPenColor());
    } else {
      g2.setColor(DrawAttr.getDefaultPenColor());
    }
    float x = Math.min(x1, x2);
    float y = Math.min(y1, y2);
    float w = Math.abs(x1 - x2);
    float h = Math.abs(y1 - y2);
    g2.draw(new Ellipse2D.Float(x, y, w, h));
  }

  @Override
  public Shape getOutlineShape() {
    float x = (float) Math.min(p1.getX(), p2.getX());
    float y = (float) Math.min(p1.getY(), p2.getY());
    float w = (float) Math.abs(p1.getX() - p2.getX());
    float h = (float) Math.abs(p1.getY() - p2.getY());
    return new Ellipse2D.Float(x, y, w, h);
  }

  @Override
  public int isInside(float px, float py) {
    float d1 = d;
    if (attr != null && attr.penSize > 1) {
      d1 += (attr.penSize / 2);
    }
    int result = PointOutside;
    float x1 = (float) Math.min(p1.getX(), p2.getX());
    float y1 = (float) Math.min(p1.getY(), p2.getY());
    float x2 = (float) Math.max(p1.getX(), p2.getX());
    float y2 = (float) Math.max(p1.getY(), p2.getY());
    float cx = (x1 + x2) / 2;
    float cy = (y1 + y2) / 2;
    float tx = px;
    float ty = py;
    if ((x2 - x1) < (3 * d1)) {
      x1 -= d1;
      x2 += d1;
    }
    if ((y2 - y1) < (3 * d1)) {
      y1 -= d1;
      y2 += d1;
    }
    if (at != null) {
      float[] src = { tx - cx, ty - cy };
      float[] dst = new float[2];
      if (inv != null) {
	inv.transform(src, 0, dst, 0, 1);
	tx = dst[0] + cx;
	ty = dst[1] + cy;
      }
    }
    if ((tx >= x1 - 2.0f) && (tx <= x2 + 2.0f) && (ty >= y1 - 2.0f)
	&& (ty <= y2 + 2.0f)) {
      double dx1 = Math.abs(tx - x1);
      double dx2 = Math.abs(tx - x2);
      double dy1 = Math.abs(ty - y1);
      double dy2 = Math.abs(ty - y2);
      if (dx1 <= d1) {
	if (dy1 <= d1) {
	  result = PointNWCorner;
	} else if (dy2 <= d1) {
	  result = PointSWCorner;
	} else {
	  result = PointWestEdge;
	}
      } else if (dx2 <= d1) {
	if (dy1 <= d) {
	  result = PointNECorner;
	} else if (dy2 <= d1) {
	  result = PointSECorner;
	} else {
	  result = PointEastEdge;
	}
      } else if (dy1 <= d1) {
	result = PointNorthEdge;
      } else if (dy2 <= d1) {
	result = PointSouthEdge;
      }

      if (result == PointOutside) {
	double a = (x2 - x1) / 2.0 + 2.0;
	double b = (y2 - y1) / 2.0 + 2.0;
	double ccx = x1 + a;
	double ccy = y1 + b;
	double dx = tx - ccx;
	double dy = ty - ccy;
	if (dx * dx / (a * a) + dy * dy / (b * b) <= 1.0) {
	  result = PointInside;
	}
      }
    } else if (allowRotation) {
      if (isNearPoint(tx, ty, (x1 + x2) / 2, y1 - handleLength)) {
	result = PointOnRotationHandle;
      }
    }

    if (result == PointOutside) {
      double bx1 = bounds.getX();
      double by1 = bounds.getY();
      double bx2 = (bounds.getX() + bounds.getWidth());
      double by2 = (bounds.getY() + bounds.getHeight());
      if ((px >= bx1 - d1) && 
	  (px <= bx2 + d1) && (py >= by1 - d1) && 
	  (py <= by2 + d1)) {
	result = PointInBounds;
      }
    }
    return result;
  }

  @Override
  protected Map<String, Object> getXMLAttributes() {
    Map<String, Object> attrs = super.getXMLAttributes();
    if (attrs == null) {
      attrs = new HashMap<String, Object>();
    }
    attrs.put("filled", filled);
    attrs.put("showBorder", showborder);
    return attrs;
  }

  @Override
  protected void processXMLAttributes(org.w3c.dom.Element node) {
    if (node != null) {
      super.processXMLAttributes(node);

      String s;
      s = node.getAttribute("filled");
      filled = Boolean.valueOf(s);

      s = node.getAttribute("showBorder");
      showborder = Boolean.valueOf(s);
    }
  }

  /*
   * public void writeXML(CodeWriter out, int mode, String
   * namespacePrefix) { if (out != null) { String nodename =
   * getPrefixedName("EllipseShape", namespacePrefix); out.indentInc();
   * out.println("<" + nodename + " filled=\"" + filled + "\"
   * showBorder=\"" + showborder + "\">"); writeXMLBase(out, mode,
   * namespacePrefix); out.thisIndentDec(); out.println("</" + nodename +
   * ">"); } }
   * 
   * public void setAttrFromXML(org.w3c.dom.Element node, String
   * namespaceURI) { if (node != null) { String uri =
   * node.getNamespaceURI(); if (namespaceURI == null ||
   * namespaceURI.equals(uri)) { super.setAttrFromXML(node, namespaceURI);
   * 
   * String s; s = node.getAttribute("filled"); if (s != null &&
   * s.length() > 0) { try { this.filled =
   * Boolean.valueOf(s).booleanValue(); } catch (NumberFormatException ex) {} }
   * 
   * s = node.getAttribute("showBorder"); if (s != null && s.length() > 0) {
   * try { this.showborder = Boolean.valueOf(s).booleanValue(); } catch
   * (NumberFormatException ex) {} } } } }
   */

  /* return the intersection point of p and the center of the node */
  @Override
  public Point2D intersect(Point2D p, float offsetX, float offsetY) {
    if (p != null) {
      Point2D c = getCenter();
      c.setLocation(c.getX() + offsetX, c.getY() + offsetY);
      if (isInside(p) == PointOutside) {
	// find the intersection closer to p
	return Geometry.intersectionEllipseLine(getULCorner(), getLRCorner(), p, c);
      } else {
	// find the intersection closer to c
	return Geometry.intersectionEllipseLine(getULCorner(), getLRCorner(), c, p);
      }
    } else {
      return null;
    }
  }

  @Override
  public Dimension2D fitContentDimension() {
    Dimension2D dim = getContentDimension();
    float w = 0;
    float h = 0;
    float a = 0;
    float b = 0;
    if (dim != null) {
      w = (float) dim.getWidth();
      h = (float) dim.getHeight();
    }

    if (w > 0 && h > 0) {
      float t;
      if (w > 2 * h) {
	t = (float) DrawingUtilities.calculateRotationAngle(0, 0, h, h);
      } else if (h > 2 * w) {
	t = (float) DrawingUtilities.calculateRotationAngle(0, 0, w, w);
      } else {
	t = (float) DrawingUtilities.calculateRotationAngle(0, 0, w, h);
      }
      a = w / (float) Math.cos(t);
      b = h / (float) Math.sin(t);

      /*
         * System.out.println("Ellipse.fitContentDimension(): w=" + w + "; h=" +
         * h + "; t=" + t + "; a=" + a + "; b=" + b);
         */

    }
    return new Dimension2DFloat(a + 2 * nodePad, b + 2 * nodePad);
  }

}
